package com.dabai.iot.core.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dabai.common.core.enums.YnEnum;
import com.dabai.common.core.exception.ServiceException;
import com.dabai.common.core.utils.DateUtils;
import com.dabai.common.core.utils.Func;
import com.dabai.common.core.utils.LocalDateUtils;
import com.dabai.common.core.utils.StringUtils;
import com.dabai.common.mybatis.core.page.TableDataInfo;
import com.dabai.common.redis.utils.RedisUtils;
import com.dabai.common.tenant.helper.TenantHelper;
import com.dabai.iot.common.enums.BizLogCode;
import com.dabai.iot.common.enums.ConnectStatus;
import com.dabai.iot.common.enums.SecurityType;
import com.dabai.iot.common.enums.TopicSourceType;
import com.dabai.iot.common.model.DeviceInfo;
import com.dabai.iot.common.model.IotMqttStatus;
import com.dabai.iot.core.cache.CacheKey;
import com.dabai.iot.core.config.IotProperties;
import com.dabai.iot.core.convert.DeviceConvert;
import com.dabai.iot.core.dto.DeviceQuery;
import com.dabai.iot.core.entity.*;
import com.dabai.iot.core.mapper.DeviceMapper;
import com.dabai.iot.core.model.MqttLinkInfo;
import com.dabai.iot.core.service.*;
import com.dabai.iot.core.vo.DeviceSelectVO;
import com.dabai.iot.core.vo.DeviceVO;
import com.dabai.iot.core.vo.HomeLineVO;
import com.dabai.iot.mqtt.api.RemoteMqttService;
import com.dabai.iot.mqtt.model.DeviceConnectionSession;
import com.dabai.iot.mqtt.topics.SysTopics;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 设备 Service 实现类
 *
 * @author kev1n
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, DeviceEntity> implements IDeviceService {

    private final IProductService productService;
    private final IProductTopicService productTopicService;
    private final IAlarmService alarmService;
    private final IAlarmDeviceRelationService alarmDeviceRelationService;
    private final IDeviceLogService deviceLogService;
    private final IotProperties iotProperties;

    @DubboReference
    private RemoteMqttService remoteMqttService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addDevice(DeviceEntity device) {
        ProductEntity product = productService.getProductCacheById(device.getProductId());
        if (product == null) {
            throw new ServiceException("产品不存在");
        }

        device.setProductId(product.getId());
        device.setProductKey(product.getProductKey());
        device.setDataType(product.getDataType());

        if (checkDeviceSnDuplicate(device)) {
            throw new ServiceException("已经存在相同的设备序列号");
        }

        if (SecurityType.one.getValue().equals(product.getSecurityType())) {
            // 一型一密
            device.setDeviceSecret(product.getProductSecret());
        } else if (SecurityType.each.getValue().equals(product.getSecurityType())) {
            // 一机一密
            device.setDeviceSecret(RandomUtil.randomString(12));
        }

        device.setIsActive(YnEnum.NO.getValue());
        device.setIsOnline(YnEnum.NO.getValue());
        device.setIsEnabled(YnEnum.YES.getValue());
        return save(device);
    }

    @Override
    public boolean updateDevice(DeviceEntity device) {
        if (device == null || device.getId() == null) {
            return false;
        }

        String deviceSn = device.getDeviceSn();

        // 防止被恶意修改
        device.setTenantId(null);
        device.setProductId(null);
        device.setDeviceSn(null);
        boolean result = updateById(device);
        if (result) {
            // 删除缓存
            deleteCacheById(device.getId());
            deleteCacheBySn(device.getProductKey(), deviceSn);
        }
        return result;
    }

    /**
     * 校验设备序列号是否重复(企业内唯一)
     */
    boolean checkDeviceSnDuplicate(DeviceEntity device) {
        return this.lambdaQuery()
            .eq(DeviceEntity::getDeviceSn, device.getDeviceSn())
            .ne(ObjectUtil.isNotNull(device.getId()), DeviceEntity::getId, device.getId())
            .exists();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeDevice(Long id) {
        DeviceEntity device = getById(id);
        if (device == null) {
            throw new ServiceException("设备不存在");
        }

        // 检查是否有关联的告警配置
        checkRelatedAlarms(id);

        String traceId = IdWorker.get32UUID();
        String productKey = device.getProductKey();
        String deviceSn = device.getDeviceSn();

        log.info("【删除设备】traceId={}，deviceId={}", traceId, id);

        boolean result = removeById(id);
        if (result) {
            DeviceConnectionSession connectionSession = remoteMqttService.getDeviceConnectionSession(productKey, deviceSn);
            if (connectionSession != null) {
                log.info("【删除设备】设备当前在线，需关闭连接会话，traceId={}，deviceId={}", traceId, id);
                // 下线日志
                DeviceInfo deviceInfo = new DeviceInfo(device.getTenantId(), device.getProductId(), productKey, device.getId(), deviceSn, device.getDataType());
                IotMqttStatus iotMqttStatus = new IotMqttStatus(traceId, ConnectStatus.offline, BizLogCode.DELETE_DEVICE_STATUS, DateUtils.getNowDate());
                deviceLogService.saveDeviceStatusLogToTD(deviceInfo, iotMqttStatus);
                // 关闭设备的连接会话
                remoteMqttService.closeDeviceConnectionSession(traceId, productKey, deviceSn);
            }

            // 删除缓存
            deleteCacheById(device.getId());
            deleteCacheBySn(device.getProductKey(), device.getDeviceSn());
        }

        return result;
    }

    /**
     * 检查是否有关联的告警配置
     */
    private void checkRelatedAlarms(Long deviceId) {
        List<AlarmDeviceRelationEntity> relationList = alarmDeviceRelationService.lambdaQuery()
            .eq(AlarmDeviceRelationEntity::getDeviceId, deviceId)
            .list();
        if (ObjectUtil.isNotEmpty(relationList)) {
            List<Long> alarmIds = relationList.stream()
                .map(AlarmDeviceRelationEntity::getAlarmId)
                .distinct()
                .toList();
            List<AlarmEntity> alarmList = alarmService.lambdaQuery().in(AlarmEntity::getId, alarmIds).list();
            String alarmNames = alarmList.stream().map(AlarmEntity::getAlarmName).collect(Collectors.joining(","));
            throw new ServiceException(StringUtils.format("该设备无法删除，存在关联的告警配置[{}]", alarmNames));
        }
    }

    @Override
    public DeviceVO detailById(@NonNull Long id) {
        DeviceEntity device = this.getById(id);
        return resolveDeviceVO(device);
    }

    @Override
    public IPage<DeviceVO> getDevicePage(IPage<DeviceEntity> page, DeviceQuery deviceQuery) {
        QueryWrapper<DeviceEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
            .eq(Func.isNotEmpty(deviceQuery.getId()), DeviceEntity::getId, deviceQuery.getId())
            .eq(Func.isNotEmpty(deviceQuery.getTenantId()), DeviceEntity::getTenantId, deviceQuery.getTenantId())
            .eq(Func.isNotEmpty(deviceQuery.getProductId()), DeviceEntity::getProductId, deviceQuery.getProductId())
            .eq(Func.isNotEmpty(deviceQuery.getDeviceSn()), DeviceEntity::getDeviceSn, deviceQuery.getDeviceSn())
            .like(Func.isNotEmpty(deviceQuery.getDeviceName()), DeviceEntity::getDeviceName, deviceQuery.getDeviceName())
            .eq(Func.isNotEmpty(deviceQuery.getIsOnline()), DeviceEntity::getIsOnline, deviceQuery.getIsOnline())
            .eq(Func.isNotEmpty(deviceQuery.getIsActive()), DeviceEntity::getIsActive, deviceQuery.getIsActive())
            .eq(DeviceEntity::getDelFlag, "0")
            .orderByDesc(DeviceEntity::getCreateTime);

        // 0未激活 1在线 2离线 3已禁用
        Integer deviceStatus = deviceQuery.getStatus();
        if (Func.isNotEmpty(deviceStatus)) {
            if (deviceStatus.equals(0)) {
                queryWrapper.lambda().eq(DeviceEntity::getIsActive, 0);
            } else if (deviceStatus.equals(1)) {
                queryWrapper.lambda().eq(DeviceEntity::getIsOnline, 1);
            } else if (deviceStatus.equals(2)) {
                queryWrapper.lambda().eq(DeviceEntity::getIsActive, 1).eq(DeviceEntity::getIsOnline, 0);
            } else if (deviceStatus.equals(3)) {
                queryWrapper.lambda().eq(DeviceEntity::getIsEnabled, 0);
            }
        }

        IPage<DeviceEntity> result = page(page, queryWrapper);
        return result.convert(this::resolveDeviceVO);
    }

    @Override
    public IPage<DeviceVO> getDevicePageByGroup(IPage<DeviceEntity> page, Long groupId, DeviceQuery deviceQuery) {
        MPJLambdaWrapper<DeviceEntity> wrapper = JoinWrappers.lambda(DeviceEntity.class)
            .selectAll(DeviceEntity.class)
            .innerJoin(DeviceGroupRelationEntity.class, on -> on
                .eq(DeviceGroupRelationEntity::getDeviceId, DeviceEntity::getId)
                .eq(DeviceGroupRelationEntity::getGroupId, groupId)
            )
            .eq(Func.isNotEmpty(deviceQuery.getDeviceSn()), DeviceEntity::getDeviceSn, deviceQuery.getDeviceSn())
            .eq(Func.isNotEmpty(deviceQuery.getProductId()), DeviceEntity::getProductId, deviceQuery.getProductId())
            .orderByDesc(DeviceEntity::getCreateTime);

        IPage<DeviceEntity> result = baseMapper.selectJoinPage(page, DeviceEntity.class, wrapper);
        return result.convert(this::resolveDeviceVO);
    }

    @Override
    public List<DeviceSelectVO> select(DeviceQuery deviceQuery) {
        List<DeviceEntity> list = this.lambdaQuery()
            .select(DeviceEntity::getId, DeviceEntity::getDeviceName, DeviceEntity::getDeviceSn)
            .eq(Func.isNotEmpty(deviceQuery.getProductId()), DeviceEntity::getProductId, deviceQuery.getProductId())
            .eq(Func.isNotEmpty(deviceQuery.getDeviceSn()), DeviceEntity::getDeviceSn, deviceQuery.getDeviceSn())
            .like(Func.isNotEmpty(deviceQuery.getDeviceName()), DeviceEntity::getDeviceName, deviceQuery.getDeviceName())
            .orderByDesc(DeviceEntity::getCreateTime)
            .list();

        return list.stream().map(DeviceConvert.INSTANCE::convertSelectVO).collect(Collectors.toList());
    }

    @Override
    public DeviceEntity getDeviceById(Long deviceId) {
        return getById(deviceId);
    }

    @Override
    public DeviceEntity getDeviceCacheById(Long deviceId) {
        String cacheKey = CacheKey.getDeviceIdKey(deviceId);
        DeviceEntity deviceCache = TenantHelper.ignore(() -> RedisUtils.getCacheObject(cacheKey));
        if (deviceCache != null) {
            return deviceCache;
        }

        final DeviceEntity device = getById(deviceId);
        if (device != null) {
            deviceCache = device;
            TenantHelper.ignore(() -> RedisUtils.setCacheObject(cacheKey, device));
        }
        return deviceCache;
    }

    @Override
    public DeviceEntity getDeviceCacheBySn(@NonNull String productKey, @NonNull String deviceSn) {
        String cacheKey = CacheKey.getDeviceSnKey(productKey, deviceSn);
        DeviceEntity deviceCache = TenantHelper.ignore(() -> RedisUtils.getCacheObject(cacheKey));
        if (deviceCache != null) {
            return deviceCache;
        }

        final DeviceEntity device = getBySn(productKey, deviceSn);
        if (device != null) {
            deviceCache = device;
            TenantHelper.ignore(() -> RedisUtils.setCacheObject(cacheKey, device));
        }
        return deviceCache;
    }

    /**
     * 查询设备(数据库)
     */
    private DeviceEntity getBySn(@NonNull String productKey, @NonNull String deviceSn) {
        return this.lambdaQuery()
            .eq(DeviceEntity::getProductKey, productKey)
            .eq(DeviceEntity::getDeviceSn, deviceSn)
            .one();
    }

    private DeviceVO resolveDeviceVO(DeviceEntity device) {
        if (device == null) {
            return null;
        }

        DeviceVO vo = DeviceConvert.INSTANCE.convertVO(device);

        ProductEntity product = productService.getProductCacheById(vo.getProductId());
        if (product != null) {
            vo.setProductName(product.getProductName());
        }

        return vo;
    }

    @Override
    public TableDataInfo<ProductTopicEntity> getTopicPage(IPage<ProductTopicEntity> page, @NonNull Long deviceId, @NonNull String topicSourceType, String topicDirection) {
        DeviceEntity device = this.getById(deviceId);
        if (device == null) {
            throw new ServiceException("设备不存在");
        }

        if (TopicSourceType.system.getValue().equals(topicSourceType)) {
            List<ProductTopicEntity> topicList = productTopicService.selectList(device.getProductId(), topicSourceType, topicDirection);
            for (ProductTopicEntity productTopic : topicList) {
                productTopic.setTopic(productTopic.getTopic().replaceAll(SysTopics.DEVICE_SN_ESCAPE, device.getDeviceSn()));
            }
            return TableDataInfo.build(topicList);
        } else {
            TableDataInfo<ProductTopicEntity> result = productTopicService.selectPage(page, device.getProductId(), TopicSourceType.custom.getValue(), topicDirection);
            result.getRows().forEach(productTopic -> {
                if (productTopic.getTopic().contains(SysTopics.DEVICE_SN)) {
                    productTopic.setTopic(productTopic.getTopic().replaceAll(SysTopics.DEVICE_SN_ESCAPE, device.getDeviceSn()));
                }
            });
            return result;
        }
    }

    @Override
    public boolean setOnlineStatus(@NonNull Long id, @NonNull YnEnum isOnline) {
        DeviceEntity device = getById(id);
        if (device == null) {
            throw new ServiceException("设备不存在");
        }

        Date now = DateUtil.date();
        LambdaUpdateWrapper<DeviceEntity> updateWrapper = Wrappers.<DeviceEntity>update().lambda()
            .eq(DeviceEntity::getId, id)
            .set(DeviceEntity::getIsOnline, isOnline.getValue());

        if (YnEnum.YES.equals(isOnline) && YnEnum.NO.getValue().equals(device.getIsOnline())) {
            updateWrapper.set(DeviceEntity::getLastOnlineTime, now);

            // 如果设备没有激活
            if (!YnEnum.YES.getValue().equals(device.getIsActive())) {
                updateWrapper.set(DeviceEntity::getIsActive, YnEnum.YES.getValue());
                updateWrapper.set(DeviceEntity::getActiveTime, now);
            }
        } else {
            updateWrapper.set(DeviceEntity::getLastOfflineTime, now);
        }
        boolean result = update(null, updateWrapper);
        if (result) {
            // 删除缓存
            deleteCacheById(device.getId());
            deleteCacheBySn(device.getProductKey(), device.getDeviceSn());
        }
        return result;
    }

    @Override
    public void setDeviceStatus(@NonNull DeviceEntity device) {
        Integer isOnline = device.getIsOnline();

        Date now = DateUtils.getNowDate();
        LambdaUpdateWrapper<DeviceEntity> updateWrapper = Wrappers.<DeviceEntity>update().lambda()
            .eq(DeviceEntity::getId, device.getId())
            .set(DeviceEntity::getIsOnline, isOnline);

        if (YnEnum.YES.getValue().equals(isOnline)) {
            updateWrapper.set(DeviceEntity::getLastOnlineTime, now);

            // 如果设备没有激活
            if (!YnEnum.YES.getValue().equals(device.getIsActive())) {
                updateWrapper.set(DeviceEntity::getIsActive, YnEnum.YES.getValue());
                updateWrapper.set(DeviceEntity::getActiveTime, now);
            }
        } else {
            updateWrapper.set(DeviceEntity::getLastOfflineTime, now);
        }
        boolean result = update(null, updateWrapper);
        if (result) {
            // 删除缓存
            deleteCacheById(device.getId());
            deleteCacheBySn(device.getProductKey(), device.getDeviceSn());
        }
    }

    /**
     * 删除缓存
     */
    private void deleteCacheById(@NonNull Long deviceId) {
        TenantHelper.ignore(() -> RedisUtils.deleteObject(CacheKey.getDeviceIdKey(deviceId)));
    }

    /**
     * 删除缓存
     */
    private void deleteCacheBySn(@NonNull String productKey, @NonNull String deviceSn) {
        TenantHelper.ignore(() -> RedisUtils.deleteObject(CacheKey.getDeviceSnKey(productKey, deviceSn)));
    }

    @Override
    public List<HomeLineVO> getDeviceCountByCreateDate(String tenantId, LocalDate startDate, LocalDate endDate) {
        // 查询指定日期的注册设备数量
        LocalDateTime startTime = startDate.atStartOfDay();
        LocalDateTime endTime = endDate.plusDays(1).atStartOfDay();
        List<HomeLineVO> homeLineVOS = baseMapper.selectDeviceCountByCreateDate(tenantId, startTime, endTime);

        // 将查询结果转换为 Map 便于快速查找
        Map<LocalDate, Long> devicesMap = homeLineVOS.stream()
            .collect(Collectors.toMap(
                HomeLineVO::getDateX,
                HomeLineVO::getCountY
            ));

        // 生成完整日期序列并补全 0 值
        return LocalDateUtils.getDateRange(startDate, endDate).stream()
            .map(date -> {
                HomeLineVO stat = new HomeLineVO();
                stat.setDateX(date);
                stat.setCountY(devicesMap.getOrDefault(date, 0L));
                return stat;
            })
            .collect(Collectors.toList());
    }

    @Override
    public MqttLinkInfo getMqttLinkInfo(Long deviceId) {
        DeviceEntity device = getById(deviceId);
        if (device == null) {
            return null;
        }

        MqttLinkInfo linkInfo = new MqttLinkInfo();
        linkInfo.setHost(iotProperties.getHost());
        linkInfo.setPort(iotProperties.getPort());
        linkInfo.setWebPort(iotProperties.getWebPort());
        linkInfo.setClientId(device.getDeviceSn());
        linkInfo.setUsername(device.getProductKey());
        linkInfo.setPassword(device.getDeviceSecret());

        return linkInfo;
    }
}
